using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;

namespace S
{
    /// <summary>
    /// the screen class represents a woz screen
    /// </summary>
    public class Screen
    {
        /// <summary>
        /// private constructor that does nothing
        /// </summary>
        private Screen()
        {
        }

        /// <summary>
        /// default screen constructor
        /// </summary>
        /// <param name="pName">the name of the screen</param>
        public Screen(string pName)
        {
            // set the name of this screen
            this.mName = pName;

            // set the screen to not be hidden
            this.mHidden = false;

            // initialize the list of entities
            this.mEntities = new List<S.Entity>();

            // initialize the list of selected entities
            this.mSelectedEntities = new List<S.Entity>();

            // initialize the bounding box of the selected entities
            this.mSelectedEntitiesBoundingBox = S.Entity.ComputeBoundingBox(this.mSelectedEntities.ToArray());

            // initialize the thumbnail image
            this.mThumbnailImage = new Bitmap(S.StaticVariables.Instance.ThumbnailSize.Width, S.StaticVariables.Instance.ThumbnailSize.Height, PixelFormat.Format32bppRgb);

            // clear the screen
            this.Clear();
        }

        /// <summary>
        /// gets or sets the name of this screen
        /// </summary>
        public string Name
        {
            get
            {
                return this.mName;
            }
            set
            {
                this.mName = value;
            }
        }

        /// <summary>
        /// gets the thumbnail image associated with this screen
        /// </summary>
        public Image Thumbnail
        {
            get
            {
                return this.mThumbnailImage;
            }
        }

        /// <summary>
        /// gets or whether the screen is hidden or not
        /// </summary>
        public bool Hidden
        {
            get
            {
                return this.mHidden;
            }
            set
            {
                this.mHidden = true;
            }
        }

        /// <summary>
        /// gets an array of the selected entities
        /// </summary>
        public S.Entity[] SelectedEntities
        {
            get
            {
                return this.mSelectedEntities.ToArray();
            }
        }

        /// <summary>
        /// gets the bounding box of the selected entities
        /// </summary>
        public Rectangle SelectedEntitiesBoundingBox
        {
            get
            {
                return this.mSelectedEntitiesBoundingBox;
            }
        }

        /// <summary>
        /// clone this screen and return a reference to the clone
        /// </summary>
        /// <param name="pName">the name to give the clone</param>
        public S.Screen Clone(string pName)
        {
            // create the new screen using the empty private constructor
            S.Screen screen = new S.Screen();

            // set the name of this screen
            screen.mName = pName;

            // set whether the screen is hidden
            screen.mHidden = this.mHidden;

            // create the entities
            screen.mEntities = new List<S.Entity>();

            // create the selected entities
            screen.mSelectedEntities = new List<S.Entity>();

            // now clone the pscreen entities to this screen
            for (int i = 0; i < this.mEntities.Count; i++)
            {
                screen.mEntities.Add(this.mEntities[i]);
            }

            // now clone the selected entities to this screen
            for (int i = 0; i < this.mEntities.Count; i++)
            {
                screen.mSelectedEntities.Add(this.mSelectedEntities[i]);
            }

            // now clone the thumbnail image
            Rectangle thumbRect = new Rectangle(0, 0, this.mThumbnailImage.Width, this.mThumbnailImage.Height);
            screen.mThumbnailImage = this.mThumbnailImage.Clone(thumbRect, PixelFormat.Format32bppRgb) as Bitmap;

            // return it
            return screen;
        }

        /// <summary>
        /// add an entity to the screen
        /// </summary>
        /// <param name="pEntity">the entity to add</param>
        public void AddEntity(S.Entity pEntity)
        {
            // add the entity
            this.mEntities.Add(pEntity);

            // update the screen 
            this.UpdateThumbnail(UpdateMode.Add, pEntity);
        }

        /// <summary>
        /// remove an entity from the screen
        /// </summary>
        /// <param name="pEntity">the entity to remove</param>
        public void RemoveEntity(S.Entity pEntity)
        {
            // remove the entity
            this.mEntities.Remove(pEntity);

            // update the screen
            this.UpdateThumbnail(UpdateMode.Remove, pEntity);
        }

        /// <summary>
        /// add a selected entity to the screen
        /// </summary>
        /// <param name="pEntity">the entity to add</param>
        public void AddSelectedEntity(S.Entity pEntity)
        {
            // add the entity
            this.mSelectedEntities.Add(pEntity);

            // update the selected entities bounding box
            this.UpdateSelectedEntitiesBoundingBox();
        }

        /// <summary>
        /// remove a selected entity from the screen
        /// </summary>
        /// <param name="pEntity">the entity to remove</param>
        public void RemoveSelectedEntity(S.Entity pEntity)
        {
            // remove the entity
            this.mSelectedEntities.Remove(pEntity);

            // update the selected entities bounding box
            this.UpdateSelectedEntitiesBoundingBox();
        }

        /// <summary>
        /// update the bouding box of the selected entity
        /// </summary>
        public void UpdateSelectedEntitiesBoundingBox()
        {
            // update the bounding box of the selected entities
            this.mSelectedEntitiesBoundingBox = S.Entity.ComputeBoundingBox(this.mSelectedEntities.ToArray());
        }

        /// <summary>
        /// draw the screen using the given graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to draw with</param>
        public void Draw(Graphics pGraphics)
        {
            // draw each entity
            foreach (S.Entity entity in this.mEntities)
            {
                entity.Draw(pGraphics);
            }
        }

        /// <summary>
        /// performs a hittest on the screen and returns the first entity this point intersects
        /// </summary>
        /// <param name="pPoint">the point of interest</param>
        /// <returns>the first entity the point intersects or null if point doesnt hit anything</returns>
        public S.Entity HitTest(Point pPoint)
        {
            // find the entity that intersects with the point
            foreach (S.Entity entity in this.mEntities)
            {
                if (entity.HitTest(pPoint.X, pPoint.Y))
                {
                    return entity;
                }
            }

            // point doesnt hit anything
            return null;
        }

        /// <summary>
        /// performs a hittest on the screen and returns the list of entities that the rectangle contains
        /// </summary>
        /// <param name="pRectangle">the rectangle to perform the hittest on</param>
        /// <returns>the list of entities that the rectangle contains</returns>
        public S.Entity[] HitTest(Rectangle pRectangle)
        {
            // initialize the array containing all the entities
            List<S.Entity> selectedEntities = new List<Entity>();

            // go through all the entities and see if their bounding box is contained within the rectangle
            foreach (S.Entity entity in this.mEntities)
            {
                if (pRectangle.Contains(entity.BoundingBox))
                {
                    selectedEntities.Add(entity);
                }
            }

            // return the entities
            return selectedEntities.ToArray();
        }

        /// <summary>
        /// gets the z depth of an entity
        /// </summary>
        /// <param name="pEntity">the entity whose z depth you want</param>
        /// <returns>the z depth of the entity or negative one if the entity is not in this screen</returns>
        public int GetZDepth(S.Entity pEntity)
        {
            return this.mEntities.IndexOf(pEntity);
        }

        /// <summary>
        /// brings the entity to the front and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to bring to front</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int BringToFront(S.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only bring to front if its not already in the front
            if (originalZ != this.mEntities.Count - 1)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Add(pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// sends the entity to the back and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to send to back</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int SendToBack(S.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only send to back if its not already back
            if (originalZ != 0)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(0, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// brings the entity forward and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to bring forward</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int BringForward(S.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only bring forward if its not already as forward as possible
            if (originalZ < this.mEntities.Count - 1)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(originalZ + 1, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// sends the entity backward and returns its original depth
        /// </summary>
        /// <param name="pEntity">the entity to send backward</param>
        /// <exception cref="ArgumentException">if the entity is not found in the screen</exception>
        /// <returns>the original z depth</returns>
        public int SendBackward(S.Entity pEntity)
        {
            // first check if this entity is in the screen
            if (!this.mEntities.Contains(pEntity))
            {
                throw new ArgumentException("Entity not found in {0}.", this.mName);
            }

            // now get the entity z index
            int originalZ = this.mEntities.IndexOf(pEntity);

            // only send backward if its not already as backward as possible
            if (originalZ > 0)
            {
                // remove it from this position
                this.mEntities.RemoveAt(originalZ);

                // add it to the end of the list
                this.mEntities.Insert(originalZ - 1, pEntity);
            }

            // return the original z
            return originalZ;
        }

        /// <summary>
        /// clear the screen
        /// </summary>
        public void Clear()
        {
            // clear the list of entities
            this.mEntities.Clear();

            // update the screen
            this.UpdateThumbnail(UpdateMode.Clear, null);
        }

        /// <summary>
        /// updates the thumbnail image
        /// </summary>
        /// <param name="pMode">the mode of the update</param>
        /// <param name="pEntity">the entity to use for adding or removing modes</param>
        private void UpdateThumbnail(UpdateMode pMode, S.Entity pEntity)
        {
            // create a graphics object for the thumbnail image
            /*Graphics thumbGraphics = Woz.InkSpaceConverter.Instance.InitializeInkGraphics(Graphics.FromImage(this.mThumbnailImage));
            thumbGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            // setup the thumbnail image
            thumbGraphics.PageScale *= S.StaticVariables.Instance.ThumbnailRatio;

            // deal with the update according to the mode
            switch (pMode)
            {
                case UpdateMode.Clear:
                    {
                        // clear the screen to the initial background color
                        thumbGraphics.Clear(S.StaticVariables.Instance.InitialBackgroundColor);
                    }
                    break;

                case UpdateMode.Add:
                    {
                        // add the entity to the thumbnail
                        pEntity.Draw(thumbGraphics);
                    }
                    break;

                case UpdateMode.Remove:
                    {
                        //// get the bounding box of the entity to remove
                        //Rectangle thumbBoundingBox = pEntity.BoundingBox;

                        //// initialize some useful variables
                        //PointF conversionRate = S.InkSpaceConverter.Instance.ConversionRate;
                        //float thumbZoom = Woz.StaticVariables.Instance.ThumbnailRatio;

                        //// inflate the bounding boxes to avoid dirt
                        //thumbBoundingBox.Inflate((int)(9.0f * conversionRate.X * thumbZoom + 0.5f), (int)(9.0f * conversionRate.Y * thumbZoom + 0.5f));

                        //// clip the graphics objects to that bounding box
                        //thumbGraphics.SetClip(thumbBoundingBox);

                        // clear the graphics objects
                        thumbGraphics.Clear(S.StaticVariables.Instance.InitialBackgroundColor);

                        // now redraw all the entities
                        foreach (S.Entity entity in this.mEntities)
                        {
                            // tell this entity to draw itself on the thumbnail
                            entity.Draw(thumbGraphics);
                        }
                    }
                    break;
            }

            // dispose graphics object
            thumbGraphics.Dispose();*/
        }

        /// <summary>
        /// screen update modes
        /// </summary>
        private enum UpdateMode
        {
            /// <summary>
            /// clears the screen so entity should be null
            /// </summary>
            Clear,

            /// <summary>
            /// adds the provided entity to the screen
            /// </summary>
            Add,

            /// <summary>
            /// removes the provided entity from the screen
            /// </summary>
            Remove
        }

        /// <summary>
        /// the entities that are on this screen
        /// </summary>
        private List<S.Entity> mEntities;

        /// <summary>
        /// the selected entities
        /// </summary>
        private List<S.Entity> mSelectedEntities;

        /// <summary>
        /// the bounding box of the selected entities
        /// </summary>
        private Rectangle mSelectedEntitiesBoundingBox;

        /// <summary>
        /// save the screen thumbnail image
        /// </summary>
        private Bitmap mThumbnailImage;

        /// <summary>
        /// whether this screen is hidden or not
        /// </summary>
        private bool mHidden;

        /// <summary>
        /// the name of this screen
        /// </summary>
        private string mName;
    }
}
